﻿// Turtle Graphics
//===============================================================================
// Copyright 2009 Jason Hogg.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.

using System;
using System.Collections.Generic;   // Dictionary<T, U>
using System.Reflection;
using System.IO;
using System.Dataflow;              // DynamicParser, GraphBuilder
using System.Dataflow.Languages;
using System.Threading;             // Thread
using Microsoft.M.Grammar;          // MGrammarCompiler
using System.Text;
using Microsoft.Build.Framework;

// TODO: After an error occurs the prcess is not releasing the .mx reference. I think i have fixed this one now. 
// TODO: Add error handling in case input.logo file is not found
// TODO: Cleanup the program.cs file - logic is duplicated from below - turn into unit tests instead

namespace TurtleGraphics
{
    /// <summary>
    /// Use Mg's lexer and parser to parse the input syntax, validating it and generating an AST
    /// which we then turn into a form that our interpreter can process.
    /// </summary>
    public class TurtleGraphicsParser
    {
        string imageFileName = String.Empty;
        string languageName = String.Empty;
        string input = String.Empty;

        //StringBuilder pgmOut = new StringBuilder();             // Program output
        StringBuilder logoTrace = new StringBuilder();          // Trace information - complete program trace
        Stack<int> loopStack = new Stack<int>();                // Used to process nested loops
        Dictionary<string, string> programFunctions = new Dictionary<string, string>();

        public StringBuilder LogoTrace
        {
            get { return logoTrace; }
            set { logoTrace = value; }
        }
        
        public TurtleGraphicsParser()
        {
            // Setting MgTarget to Mgx in your .csproj creates an .mgx file embedded into your assembly 
            // with the same name as the assembly.
            imageFileName = "Logo.mx";
            languageName = "Languages.Logo";
            input = @"LogoSamples\Input.logo";
        }

        public TurtleGraphicsParser(string fileName, string languageName, string input)
        {
            this.imageFileName = fileName;
            this.languageName = languageName;            
            this.input = input;
        }

        public object Parse()
        {
            object rootNode;
            try
            {
                BufferingErrorReporter ber = new BufferingErrorReporter();

                DynamicParser language = null;

                // Assumes the logo parser generator exists in the same directory as the binaries
                FileStream fs = new FileStream(this.imageFileName, FileMode.Open);
                language = DynamicParser.LoadFromStream(fs, languageName);

                // Take input text and parse it into the parser for tokenization and parsing
                TextReader tr = new StringReader(input);
                TextReaderTextStream trts = new TextReaderTextStream(tr);
                //ErrorReporter errReporter = ErrorReporter.Standard;
                rootNode = language.Parse(trts, ber);
                if (ber.HasErrors)
                {
                    Console.WriteLine("An error occurred");
                    Console.WriteLine(ber.ErrorCount);
                    StringBuilder errorMessage = new StringBuilder();

                    foreach (ErrorInformation ei in ber.Errors)
                    {
                        errorMessage.Append(ei.ToString() + System.Environment.NewLine);
                    }
                    // TODO: More elegant way of closing the file
                    fs.Close();
                    throw new ApplicationException("Syntax error." + errorMessage);
                }
                fs.Close();
            }
            catch (Exception ex)
            {
                throw;
            }
            return rootNode;
        }

        // Return the final parsed listing of the program
        public string EnumerateOutputTree(object rootNode)
        {
            try
            {
                return EnumerateOutputTree(new GraphBuilder(), rootNode, 0);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        private string EnumerateOutputTree(GraphBuilder builder, object node, int level)
        {
            StringBuilder pgmOut = new StringBuilder();
            bool insideLoop = false;
            bool insideFunction = false;
            bool skipInstruction = false;
            string functionName = String.Empty;
            string callName = String.Empty;
            int loopCount = 0;
            int firstSpace = 0;
            string tempPgm = string.Empty;
            level++;
            object siblingNode;
            
            string command = String.Empty;

            // Enumerate all child values
            foreach (object childNode in builder.GetSequenceElements(node))
            {
                if (childNode == null)
                    continue;

                if (skipInstruction)
                {
                    skipInstruction = false;
                    continue;
                }

                if (childNode is string)
                {
                    string instruction = childNode.ToString();
                    command += instruction + " ";

                    DisplayValue(level, "Matched Input", childNode);
                       
                    switch (childNode.ToString().ToUpper().Trim())
                    {
                        case("LOOP"):
                            // We are at the start of a loop
                            insideLoop = true;
        
                            // Next value is the loop count
                            siblingNode = builder.GetSequenceElementAt(node, 1);
                            if (!(int.TryParse(siblingNode.ToString(), out loopCount)))
                                loopCount = -1;
                            loopStack.Push(loopCount);
                            skipInstruction = true;
                            break;  

                        case ("FUNCTION"):
                            // We are at the start of a function
                            insideFunction = true;
                            functionName = builder.GetSequenceElementAt(node, 1).ToString();
                            skipInstruction = true;
                            break;

                        case ("CALL") : 
                            callName = builder.GetSequenceElementAt(node, 1).ToString();
                            pgmOut.Append(programFunctions[callName]);
                            callName = String.Empty;
                            break;

                        default: 
                            // When we aren't inside a loop or a function just add instructions to the program
                            pgmOut.Append(instruction + " ");
                            break;
                    }

                    // TODO: Look into nesting variations - eg - call from within a function 
                 
                    continue;
                }

                // Retrieve child nodes
                Console.WriteLine("Popping in at level " + level.ToString());
                pgmOut.Append(EnumerateOutputTree(builder, childNode, level));
                Console.WriteLine("Popping out at level " + level.ToString() + " " + insideFunction);

                // We are at the end of a loop. Retrieve the earlier loop count
                // and then generate that number of instructions to repeat
                if (insideLoop)
                {
                    loopCount = loopStack.Pop();

                    tempPgm = pgmOut.ToString();
                    for (int loop = 1; loop < loopCount; loop++)
                        pgmOut.Append(tempPgm);
                    loopCount = -1;
                    insideLoop = false;
                }
                else if (insideFunction)
                {
                    // TODO: Add exception check for duplicate entries 
                    //firstSpace = pgmOut.ToString().IndexOf(' ');
                    //functionName = pgmOut.ToString().Substring(0, firstSpace);
                    //tempPgm = pgmOut.ToString().Substring(firstSpace + 1);
                    tempPgm = pgmOut.ToString();
                    programFunctions.Add(functionName, tempPgm);
                    insideFunction = false;
                    functionName = String.Empty;
                    pgmOut = new StringBuilder();
                }
            }
            
            return pgmOut.ToString();
        }

        private void DisplayValue(object value)
        {
            if (value.ToString().Length > 0)
                logoTrace.Append(value);
        }

        private void DisplayValue(int level, string name, object value)
        {
            logoTrace.Append(new string(' ', level * 4) + " " + name + " == " + value + Environment.NewLine);
        }

    }
}
